<?php
/**
 * Template parser.
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2006-08-07
 * @package smart
 */
class rail_template
{
	/**
	 * Template path.
	 * @var String
	 */
	private $template;
	/**
	 * Template content.
	 * @var String
	 */
	private $templateContent;
	/**
	 * All Tags ar embedded in thies chars.
	 * @var String
	 */
	private $tagIndicator = '~';
	/**
	 * Some Tags are invisible by default.
	 * @var String
	 */
	private $stealthIndicator = '@';
	/**
	 * Some Tags are replaced by script vars.
	 * @var String
	 */
	private $xIndicator = '?';
	/**
	 * Special Block char.
	 * @var String
	 */
	private $blockIndicator = ':';
	/**
	 * Max Length of all Tags.
	 * @var int
	 */
	private $maxTagLength = 29;
	/**
	 * Array with keys and values. For each ist replaceTag called by output.
	 * @var array
	 */
	private $tagRegister = array(); // Tags als Schl�ssel zu Werten in einem Array.

	/**
	 * Reference to currently active SmartError instanz.
	 * @var SmartError
	 */
	protected $errorLevel = 1; // 1 = only errors, 2 = errors and warnings
	/**
	* Reference to currently active SmartLable instanz.
	* @var  SmartLable
	*/
	protected $lableObj; // der aktuelle NameBuilder


	/**
	 * Read template and set error handle.
	 * @param string $template The MySQL server. Default value is localhost.
	 * @param SmartError $errorObj Reference to SmartError instanz. Default value is a new SmartError.
	 */
	public function __construct( $template ){
		$this->template = $template;
		// Default Lable Object
		$this->lableObj = new SmartLable_Value();
		// get file content
	    $this->getContent();

	   	// register all block tags
		$blocks = $this->getAllTagsStartsBy( $this->blockIndicator );
		for($i=0; $i<count($blocks); $i++ ){
			$this->register_tag( $blocks[$i] );
			$this->register_tag( substr($blocks[$i], strlen( $this->blockIndicator ) ).$this->blockIndicator );
		}

		$stealthTags = $this->getAllTagsStartsBy( $this->stealthIndicator );
		for($i=0; $i<count($stealthTags); $i++ ) $this->register_tag( $stealthTags[$i] , "" );
	}

	/**
	 * Returned the substring between start and stop tag and replace the substring with replace string. If the content
	 * Throw an error, if stop tag dosn't exist.
	 * @exception 7001: block dosen't start
	 * @param string $firstString Block starts with thies string.
	 * @param string $lastString Block ends with thies string.
	 * @param string $replace Block will replaced by thies string.
	 * @return string
	 */
	private function cutBlock( $firstString, $lastString, $replace ){
		$content = $this->templateContent;

		#try{
			// check wheather firstString is in content
			#if( $this->errorLevel > 1 && !strpos( $content, $firstString ) ){
			#	throw new Exception( $firstString.": block dosn't exist in ".$this->template, 7001 );
			#}

			// check wheather lastString is in content
			if( !strpos( $content, $lastString ) ){
				throw new Exception( $lastString.": block dosn't stop in ".$this->template, 7001 );
			}

		    $blockStartBy = strpos( $content, $firstString );
	   		$blockEndBy =  	$blockStartBy +
	   						strpos( substr( $content, $blockStartBy ), $lastString ) -
	   						strlen( $firstString );

	   		$block = substr( $content, $blockStartBy + strlen( $firstString ), $blockEndBy - $blockStartBy );

	   		// remove the block from content variable (global !)
	        $this->templateContent = 	substr(	$content, 0, $blockStartBy ).
	        		  					$replace.
	        		  					substr( $content, $blockEndBy + strlen( $firstString ) + strlen( $lastString ) );
	        $this->register_tag( $replace );

			return $block; // ruturned the cutted block
		#}
		#catch( Exception $e ){
		#	$this->errorObj->newError( $e );
		#}
	}

	/**
	 * Returned all Tags in template starts with specified string.
	 * @param string $tagPart First part of tags.
	 * @return array
	 */
	private function getAllTagsStartsBy( $tagPart ){
		$content = $this->templateContent;
		$tags = array();
		$subTag = $this->tagIndicator.$tagPart;
  		while( strpos( $content, $subTag ) ){
			$tagStartBy = strpos( $content, $subTag );
			$tagEndBy = $tagStartBy + strpos( substr( $content, $tagStartBy + 1 ), $this->tagIndicator );
			$tag = substr( $content, $tagStartBy + 1, $tagEndBy - $tagStartBy );
			$content = substr( $content, $tagEndBy );
			if( strlen($tag) < $this->maxTagLength ){
				$tags[] = $tag;
			}
		}
		return $tags;
	}

	/**
	 * Read template file.
	 * @exception 700: can't read template file
	 */
	private function getContent() {
		#try{
			$fp = fopen($this->template, "r");
		#	if ( !is_file($this->template) || !$fp ) throw new Exception( "Can't open template file ".$this->template, 700 );
			// write file content to class var
			while ( !feof($fp) )
				$this->templateContent .= fgets($fp, 1024);
		#}
		#catch( Exception $e ){
		#	$this->errorObj->newError( $e );
		#}
	}

	/**
	 * Returned currently error handle.
	 * @return SmartError
	 */
	public function getLableObj(){
		return $this->lableObj;
	}

		/**
	 * Replace specified tag with list of <option> elements. If array is a set's of fieldname=>value, write for
	 * each set a <option> with valuesKey as value. Otherwise write for eatch array element a <option>.
	 * @param string $tag First part of tags.
	 * @param array $array set's of fieldname=>value or #=>values
	 * @param string $defaultValue Default value for <select>
	 * @param string $valuesKey Value Row in set's of fieldname=>value
	 */
	public function html_checkboxList( $tag, $array, $inputAttributes = "", $valuesKey = "id" ){
		for($i=0; $i<count($array); $i++){

			$checkboxs .=   "<p><input ".
								( $array[$i]["checked"]?"checked=\"checked\" ":"" ).
								"type=\"checkbox\" ".
								"name=\"".$tag."[]\" ".
								"value=\"".$array[$i][$valuesKey]."\" ".
								"$inputAttributes>".
							"</input>".
							" ".$this->lableObj->getLable( $array[$i] ).
					"</p>";
		}
		$this->register_tag( $tag, $checkboxs );
		return $this->lableObj;
	}

	/**
	 * Replace specified tag with list of <option> elements. If array is a set's of fieldname=>value, write for
	 * each set a <option> with valuesKey as value. Otherwise write for eatch array element a <option>.
	 * @param string $tag First part of tags.
	 * @param array $array set's of fieldname=>value or #=>values
	 * @param string $defaultValue Default value for <select>
	 * @param string $valuesKey Value Row in set's of fieldname=>value
	 */
	public function html_selectOptions( $tag, $array, $defaultValue = "null", $valuesKey = "id" ){
		$arrayKeys = array_keys( $array );
		$options = "";
		if( is_array( $array[ $arrayKeys[0] ] ) ){
			for($i=0; $i<count($array); $i++){
				// <option> Tag erzeugen
				$option = "";
				if( $defaultValue != "null" && $array[$i][ $valuesKey ] == $defaultValue ) $option = "<option value='".$array[$i][ $valuesKey ]."' selected >";
				else $option = "<option value='".$array[$i][ $valuesKey ]."'>";
				$option .= $this->lableObj->getLable( $array[$i] );
				$option .= "</option>\\n";
				// concat options
				$options .= $option;
			}
		}
		else if( is_nan( $arrayKeys[0] ) ){
			for($i=0; $i<count($array); $i++){
				if( $defaultValue != "null" && $array[ $arrayKeys[$i] ] == $defaultValue ) $option = "<option value='".$array[ $arrayKeys[$i] ]."' selected >";
				else $option = "<option value='".$array[ $arrayKeys[$i] ]."'>";
				$option .= $this->lableObj->getLable( $array[$i] );
				$option .= "</option>\\n";
				// concat options
				$options .= $option;
			}
		}
		else{
			$options .= "<option>".$array[$i]."</option>\\n";
		}
		$this->register_tag( $tag, $options );
		return $this->lableObj;
	}

	/**
	 * Returned parsed template.
	 * @return string
	 */
	public function parse(){
		while( list( $key, $value ) = each( $this->tagRegister ) ){
			$this->set_tag( $key, $value );
		}

				// register all x and stealth tags
		$xTags = $this->getAllTagsStartsBy( $this->xIndicator );
		for($i=0; $i<count($xTags); $i++ ){
			$varname = substr( $xTags[$i], 1 );
			global $$varname;
			$this->set_tag( $xTags[$i] , $$varname );
		}

		return $this->templateContent;
	}

	public function register_array( $array ){
		$count = 0;
		while( list( $tag, $value ) = each($array) ){
			$update = $this->register_tag( $tag, $value );
			if( $update ) $count++;
		}
		return $count;
	}

	/**
	 * Register a tag or update the value of an tag. returned the old value, if exist. Otherwise returned false.
	 * @param string $tag Tag to register.
	 * @param string $value Value for the tag.
	 * @return string|bool
	 */
	public function register_tag( $tag, $value = "" ){
		if( isset( $this->tagRegister[ $tag ] ) ){
			$oldValue = $this->tagRegister[ $tag ];
			unset( $this->tagRegister[ $tag ] );
		}
		else if( isset( $this->tagRegister[ $this->xIndicator.$tag ] ) ){
			$oldValue = $this->tagRegister[ $this->xIndicator.$tag ];
			unset( $this->tagRegister[ $this->xIndicator.$tag ] );
		}
		else if( isset( $this->tagRegister[ $this->stealthIndicator.$tag ] ) ){
			$oldValue = $this->tagRegister[ $this->stealthIndicator.$tag ];
			unset( $this->tagRegister[ $this->stealthIndicator.$tag ] );
		}

		$this->tagRegister[ $tag ] = $value; // add or update variable
		if( isset($oldValue)) return $oldValue;
		else return false;
	}

	/**
	 * Replace all occurrences of tag with value.
	 * @exception 7002: tag dosen't exist
	 * @param string $tag Tag to replace.
	 * @param string $value Value for the tag.
	 */
	public function set_tag( $tag, $value ) {
		#try{
			// throw an error if tag dosent exist
			#if( $this->errorLevel > 1 &&
			#	!strpos($this->templateContent, $this->string2tag( $tag ) )  &&
			#	!strpos($this->templateContent, $this->string2tag( $this->xIndicator.$tag ) )  &&
			#	!strpos($this->templateContent, $this->string2tag( $this->stealthIndicator.$tag ) ) ){
			#		throw new Exception( $tag .": tag dosen't exist in ".$this->template.". value='".$value."'", 7002 );
			#}

			$this->templateContent = str_replace( $this->string2tag( $tag ), $value, $this->templateContent );
			$this->templateContent = str_replace( $this->string2tag( $this->xIndicator.$tag ), $value, $this->templateContent );
			$this->templateContent = str_replace( $this->string2tag( $this->stealthIndicator.$tag ), $value, $this->templateContent );
			unset( $this->tagRegister[ $tag ] );
			unset( $this->tagRegister[ $this->xIndicator.$tag ] );
			unset( $this->tagRegister[ $this->stealthIndicator.$tag ] );
		#}
		#catch( Exception $e ){
		##	$this->errorObj->newError( $e );
		#}
	}

	/**
	 * Replace all occurrences of tag with value1 if having is true, otherwise replase with value2.
	 * If value2 isn't set, empty string is used.
	 * @param string $tag Tag to replace.
	 * @param string $bool Boolean.
	 * @param string $value Value for the tag.
	 * @param string $alt Alternetive value for the tag.
	 * @return bool
	 */
	public function set_atag( $tag, $bool, $value, $alt = "" ){
		if( $bool ){
			$this->set_tag( $tag, $value );
			return $value;
		}
		else{
			$this->set_tag( $tag, $alt );
			return $alt;
		}
	}

	/**
	 * Set new error handle.
	 * @param SmartError $obj String for a new tag.
	 */
	public function setLableObj( $obj ){
		$this->lableObj = $obj;
	}

	/**
	 * Remove block from template if boll is false. Replace block with alt, if set and bool is false.
	 * @exception 7001: block dosen't start
	 * @param string $tag Block tag.
	 * @param string $bool Value for the tag.
	 * @param string $alt Alternative string for block.
	 */
	public function enable_block( $tag, $bool ) {
		$content = $this->templateContent;
		$preTag = $this->tagIndicator.$tag.$this->blockIndicator.$this->tagIndicator;
		$postTag = $this->tagIndicator.$this->blockIndicator.$tag.$this->tagIndicator;

	#	try{
			#if( !strpos( $this->templateContent, $preTag ) || !strpos( $this->templateContent, $postTag ) ){
			#	throw new Exception( $lastString.": block dosn't exist in ".$this->template, 7001 );
			#}

			while( strpos( $content, $preTag ) ){
				if( !$bool ){
					$newTag = uniqid("autotag_");
					$this->cutBlock(	$this->string2tag( $tag.$this->blockIndicator ),
										$this->string2tag( $this->blockIndicator.$tag ),
										$this->string2tag( $newTag ) );

					$this->set_tag( $newTag, "" );
					unset( $this->tagRegister[ $this->blockIndicator.$tag] );
					unset( $this->tagRegister[ $tag.$this->blockIndicator] );
				}
				$content = substr( $content, strpos( $content, $postTag ) + strlen( $postTag ) );
			}
		#}
		#catch( Exception $e ){
		#	$this->errorObj->newError( $e );
		#}
	}

	/**
	 * Parse the specified block with set's of array. Copyed the block for eatch array row.
	 * @param string $tag Block tag.
	 * @param array $array set's of tag=>value.
	 */
	public function spool_block( $tag, $array ){
		$tagNew = uniqid("autotag_");
		$block = $this->cutBlock( $this->string2tag( $tag.$this->blockIndicator ),  $this->string2tag( $this->blockIndicator.$tag ), $this->string2tag( $tagNew ) );
		unset( $this->tagRegister[$this->blockIndicator.$tag] );
		unset( $this->tagRegister[$tag.$this->blockIndicator] );
		$newBlock = $this->spoolBlockRec( $block, $array );
		$this->set_tag( $tagNew, $newBlock );
	}

	/**
	 * Replace all occurrences of array->key with array->value. if array->key is an array
	 * the key is interpretet as new block and will recrusice worked by calling it self.
	 * If array->key is boolean, the key is interpretet as block and will shown if value is
	 * true and hidden otherwise.
	 * @param string $block Block tag.
	 * @param array $array set's of tag=>value.
	 */
	private function spoolBlockRec( $block, $array ){
		$newBlock = "";
		if( !isset( $array ) || !is_array( $array ) ) return $newBlock;

		foreach( $array as $row ){
            $tmpBlock = $block;
			while( list($tag, $value ) = each($row) ){

				// value is a array or bool
				if( is_array( $value ) || is_bool( $value ) ){
					$blockTag1 = $this->string2tag( $tag.$this->blockIndicator );
					unset( $this->replace_vars[ $blockTag1 ] );
					$blockTag2 = $this->string2tag( $this->blockIndicator.$tag );
					unset( $this->replace_vars[ $blockTag2 ] );

					$blockStartBy = strpos( $tmpBlock, $blockTag1 );
					$blockEndBy =  $blockStartBy + strpos( substr( $tmpBlock, $blockStartBy ), $blockTag2 ) - strlen( $blockTag1 );
					$cuttedBlock = substr( $tmpBlock, $blockStartBy + strlen( $blockTag1 ), $blockEndBy - $blockStartBy );

					if( is_array($value) ) $replace = $this->spoolBlockRec( $cuttedBlock, $value );
					else if( is_bool($value) && $value ) $replace = $cuttedBlock;
					else if( is_bool($value) ) $replace = "";

					$tmpBlock = substr( $tmpBlock, 0, $blockStartBy ).
								$replace.
								substr($tmpBlock, $blockEndBy + strlen( $blockTag2 ) + strlen( $blockTag1 ) );
				}
				//normalfall
				else{
					$tmpBlock = str_replace( $this->string2tag( $tag ), $value, $tmpBlock );
					$tmpBlock = str_replace( $this->string2tag( $this->xIndicator.$tag ), $value, $tmpBlock );
					$tmpBlock = str_replace( $this->string2tag( $this->stealthIndicator.$tag ), $value, $tmpBlock );
					unset( $this->tagRegister[ $tag ] );
					unset( $this->tagRegister[ $this->xIndicator.$tag ] );
					unset( $this->tagRegister[ $this->stealthIndicator.$tag ] );
				}
			}

			$newBlock .= $tmpBlock;
			// next array element
		}
		return $newBlock;
	}

	/**
	 * Returned a tag. Add tagIndicator to specified string.
	 * @param string $string String for a new tag.
	 * @return string
	 */
	public function string2tag( $string ){
		return $this->tagIndicator.$string.$this->tagIndicator;
	}

} // end of Parser


/**
 * Smart Lable Builder.
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2006-06-27
 * @package smart
 */
Class SmartLable
{
	/**
	 * Returned Lable for specified set.
	 * @param array $array Set of filedname=>value.
	 * @return string
	 */
	public function getLable( $array ){
		return $array[0];
	}
}


/**
 * Smart Lable Builder Extention for value key is 'name'.
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2006-06-27
 * @package smart
 */
Class SmartLable_Name extends SmartLable
{
	/**
	 * Returned Lable for specified set.
	 * @param array $array Set of filedname=>value.
	 * @return string
	 */
	public function getLable( $array ){
		return $array["name"];
	}
}

/**
 * Smart Lable Builder Extention for value key is 'value'.
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2006-06-27
 * @package smart
 */
Class SmartLable_Value extends SmartLable
{
	/**
	 * Returned Lable for specified set.
	 * @param array $array Set of filedname=>value.
	 * @return string
	 */
	public function getLable( $array ){
		return $array["value"];
	}
}

/**
 * Smart Lable Builder Extention for value key is 1.
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2006-06-27
 * @package smart
 */
Class SmartLable_Simple extends SmartLable
{
	/**
	 * Returned Lable for specified set.
	 * @param array $array Set of filedname=>value.
	 * @return string
	 */
	public function getLable( $array ){
		return $array[1];
	}
}

?>